import re
import sys
from datetime import date
from configparser import ConfigParser
from multiprocessing import Process, Queue

from compressed_nntp_ssl import Compressed_NNTP_SSL

class InvalidRangeException(Exception):
    pass

class FetchError(Exception):
    pass

class FetcherProcess(Process):

    CMD_FETCH   = 1
    CMD_QUIT    = 2
    CMD_GETCNT  = 3

    RECYCLE_COUNT = 10000000

    nntp_fetcher = None
    nntp_connection = None

    command_queue = None
    result_queue = None

    article_count = 0

    def __init__(self, process_name, nntp_fetcher, command_queue, result_queue):
        super(FetcherProcess, self).__init__(name=process_name)
        self.nntp_fetcher = nntp_fetcher
        self.command_queue = command_queue
        self.result_queue = result_queue
        self.nntp_connection = self.nntp_fetcher.get_nntp_connection()

    def run(self):
        command, *arguments = self.command_queue.get()
        while command != self.CMD_QUIT:
            process_name = arguments[0]
            group_name = arguments[1]
            if command == self.CMD_GETCNT and process_name == self.name:
                _, _, first, last, _ = self.nntp_connection.group(group_name)
                self.result_queue.put((first, last))
            elif command == self.CMD_GETCNT:
                self.command_queue.put((self.CMD_GETCNT, process_name, group_name))
            elif command == self.CMD_FETCH:
                first_article = arguments[0]
                last_article = arguments[1]
                group_name = arguments[2]
                self.nntp_connection.group(group_name)
                if hasattr(self.nntp_connection, 'xzver'):
                    _, overviews = self.nntp_connection.xzver(first_article, last_article)
                else:
                    sys.stderr.write("Warning: xzver method not found - using over (non-compressed) command.\n")
                    _, overviews = self.nntp_connection.over((first_article, last_article))

                headers = []

                for (article_id, overview) in overviews:
                    overview['message-id'] = overview['message-id'][1:-1]
                    overview['date'] = self._fix_date_header(overview['date'])
                    if len(str(overview[':lines'])) == 0: overview[':lines'] = "0"
                    if len(str(overview[':bytes'])) == 0: overview[':bytes'] = "0"

                    headers.append(
                        [ str(article_id),
                          str(overview[':lines']),
                          str(overview[':bytes']),
                          overview['date'],
                          overview['subject'].encode("utf-8", "surrogateescape").decode("latin-1"),
                          overview['message-id'],
                          overview['from'].encode("utf-8", "surrogateescape").decode("latin-1") ] )

                self.article_count += len(headers)
                self.result_queue.put(headers)

                del headers

                if self.article_count > self.RECYCLE_COUNT:
                    self.nntp_connection.quit()
                    self.nntp_connection = self.nntp_fetcher.get_nntp_connection()
                    self.article_count = 0

            command, *arguments = self.command_queue.get()

    rfixdate1 = re.compile(".*\ \(...\)$")
    rfixdate2 = re.compile(".*\ ....$")
    rfixdate3 = re.compile(".*\ \(....\)$")
    rfixdate4 = re.compile(".*\ ...\ .....$") # Fri,10 Oct 2008 08:34:34 GMT +0200
    def _fix_date_header(self, date_):
        if len(date_) == 0:
            date_ = str(date(1970, 1, 1))
        elif self.rfixdate1.match(date_):
            date_ = date_[:-6]
        elif self.rfixdate2.match(date_):
            date_ = date_[:-4] + "+" + date_[-4:]
        elif self.rfixdate3.match(date_):
            date_ = date_[:-7]
        elif self.rfixdate4.match(date_):
            date_ = date_[:-11]
        return date_

class NNTPFetcher(object):
    FETCH_STEP = 10000

    CONFIG_FILENAME = 'indexer.ini'

    username = None
    password = None
    num_threads = None
    server_port = None
    server_host = None

    fetchers = []

    result_queue = None
    command_queue = None

    def __init__(self, num_threads=1):
        self._read_configuration()
        self.num_threads = num_threads

        self.result_queue = Queue()
        self.command_queue = Queue()

        for fetcher_rank in range(num_threads):
            fetcher = FetcherProcess("f%d" % (fetcher_rank),self, self.command_queue, self.result_queue)
            self.fetchers.append(fetcher)
            fetcher.start()

    def __del__(self):
        self.terminate()

    def terminate(self):
        for _ in range(len(self.fetchers)):
            self.command_queue.put((FetcherProcess.CMD_QUIT, None))
        for fetcher in self.fetchers:
            fetcher.join()
        self.fetchers = []

    def get_nntp_connection(self):
        return Compressed_NNTP_SSL(host=self.server_host,
            port=self.server_port, user=self.username, password=self.password)

    def _read_configuration(self):
        config = ConfigParser()
        config.read(self.CONFIG_FILENAME)
        self.username = config['server']['username']
        self.server_host = config['server']['host']
        self.server_port = config['server']['port']       
        self.password = config['server']['password']

    def get_article_count(self, group_name):
        self.command_queue.put((FetcherProcess.CMD_GETCNT, "f0", group_name))
        return self.result_queue.get()

    def fetch_article_headers(self, group_name, range_, headers, _skip_count=False):
        assert(isinstance(headers, list) and len(headers) == 0)
        assert(isinstance(group_name, str) and len(group_name) > 0)

        first, last = self.get_article_count(group_name)

        start_id = range_[0]
        end_id = range_[1]

        if start_id is None: start_id = first
        if end_id is None:   end_id = last

        if start_id < first or end_id > last:
            sys.stderr.write("%s < %s or %s > %s\n" % (start_id, first, end_id, last))
            sys.stderr.flush()
            raise InvalidRangeException()

        nsubranges = 0
        for subrange_start in range(start_id, end_id + 1, self.FETCH_STEP):
            self.command_queue.put((FetcherProcess.CMD_FETCH, subrange_start, subrange_start + self.FETCH_STEP - 1, group_name))
            nsubranges += 1

        while nsubranges > 0:
            result = self.result_queue.get()
            if type(result) != list:
                raise FetchError()
            headers.extend(result)
            nsubranges -= 1
